package toolx

import (
	"context"
	"strings"

	"gitee.com/chunanyong/zorm"
	"gitee.com/workits/pkgs/dbx"
)

type PostgreSQLTool struct {
	dbCfg dbx.Config
}

func (t *PostgreSQLTool) TableToStructInfo(dbName, tableName, ignorePrefix, structName string, isStandard bool) StructInfo {
	tb := t.getTableInfo(tableName)
	tbName := tb.Name
	if len(structName) == 0 {
		if len(ignorePrefix) > 0 {
			tbName = tbName[len(ignorePrefix):]
		}
	} else {
		tbName = structName
	}
	tbName = strings.TrimLeft(tbName, "_")

	structInfo := StructInfo{
		Name:       UderscoreToLowerCamelCase(tbName),
		StructName: UderscoreToUpperCamelCase(tbName),
		TableName:  tb.Name,
		FileName:   CamelCaseToUdnderscore(tbName),
		Comment:    tb.Comment,
		IsStandard: isStandard,
	}

	var fields []StructField
	for _, col := range tb.Fields {
		field := StructField{
			FieldName:  UderscoreToUpperCamelCase(col.Field),
			ColumnName: col.Field,
			JsonName:   UderscoreToLowerCamelCase(col.Field),
			Comment:    col.Comment,
		}

		// 是否必填项
		if col.Null == "NO" {
			field.Required = true
		}

		// 默认值
		if isStandard {
			if field.FieldName == "CreatedAt" || field.FieldName == "UpdatedAt" {
				field.Default = "time.Now()"
			}
		} else {
			if len(col.Default) > 0 {
				switch col.Default {
				case "CURRENT_TIMESTAMP":
					field.Default = "time.Now()"
				default:
					field.Default = "\"" + col.Default + "\""
				}
			}
		}

		// 数据类型转换：db -> golang
		typ := strings.ToUpper(col.Type)
		if strings.Index(typ, "TIME") == 0 || strings.Index(typ, "DATE") == 0 {
			field.Type = "time.Time"
			if structInfo.IsStandard {
				if field.FieldName != "CreatedAt" && field.FieldName != "UpdatedAt" && field.FieldName != "DeletedAt" {
					structInfo.HasTime = true
				}
			} else {
				structInfo.HasTime = true
			}
		} else if typ == "NUMBER" || typ == "DEC" || typ == "SERIAL8" || strings.Contains(typ, "NUMERIC") || strings.Contains(typ, "DECIMAL") {
			field.Type = "decimal.Decimal"
			structInfo.HasDecimal = true
		} else {
			switch typ {
			case "INT2", "INTEGER", "INT4", "SERIAL4":
				field.Type = "int"
			case "BIGINT", "INT8", "BIGSERIAL":
				field.Type = "int64"
			case "SMALLINT", "MEDIUMINT", "SERIAL2":
				field.Type = "int32"
			case "TINYINT":
				field.Type = "int8"
			case "FLOAT", "FLOAT4", "FLOAT8":
				field.Type = "float32"
			case "DOUBLE", "REAL", "DOUBLE PRECISION", "MONEY":
				field.Type = "float64"
			case "BIT", "BYTE":
				field.Type = "byte"
			case "BLOB", "BYTEA":
				field.Type = "[]byte"
			case "BOOLEAN":
				field.Type = "bool"
			default:
				field.Type = "string"
			}
		}

		// 主健
		if col.Key == "PRI" {
			structInfo.PrimaryKeyColumn = field.ColumnName
			structInfo.PrimaryKeyField = field.FieldName
			structInfo.PrimaryKeyName = field.JsonName
			structInfo.PrimaryKeyType = field.Type
			field.IsPrimaryKey = true
		}

		fields = append(fields, field)
	}
	structInfo.Fields = fields

	return structInfo
}

func (t *PostgreSQLTool) getTableInfo(tableName string) *TableInfoEntity {
	if dbx.GetDefaultDao() == nil {
		if err := dbx.InitConn(t.dbCfg); err != nil {
			panic("🙅数据库初始化失败")
		}
		zorm.FuncPrintSQL = func(ctx context.Context, sqlstr string, args []interface{}, execSQLMillis int64) {}
	}

	ctx := context.Background()

	// 获取表注释
	finder := zorm.NewFinder()
	finder.Append("select relname as name, obj_description(oid, ?) as comment from pg_class where relname = ?", "pg_class", tableName)
	tb := &TableInfoEntity{}
	if has, err := zorm.QueryRow(ctx, finder, tb); err != nil {
		panic(err)
	} else {
		if !has {
			panic("🙅数据库表不存在")
		}
	}

	// 获取字段信息
	finder = zorm.NewFinder()
	finder.Append(
		`
		select 
			a.attname as field,
			(case
				when a.attnotnull = true then 'NO'
				else 'YES'
			end) as null,
			(case
				when
					(select count(pg_constraint.*)
						from pg_constraint
						inner join pg_class on pg_constraint.conrelid = pg_class.oid
						inner join pg_attribute on pg_attribute.attrelid = pg_class.oid
						and pg_attribute.attnum = any(pg_constraint.conkey)
						inner join pg_type on pg_type.oid = pg_attribute.atttypid
						where pg_class.relname = c.relname
							and pg_constraint.contype = 'p'
							and pg_attribute.attname = a.attname) > 0 then 'PRI'
				else ''
			end) as key,
			concat_ws('', t.typname) as type,
			(case
				when a.attlen > 0 then a.attlen
				when t.typname = 'bit' then a.atttypmod
				else a.atttypmod - 4
			end) as len,
			col.column_default as default,
			(select description
				from pg_description
				where objoid = a.attrelid
					and objsubid = a.attnum) as comment
		from pg_class c ,
			pg_attribute a ,
			pg_type t ,
			information_schema.columns as col
		where c.relname = ?
			and a.attnum > 0
			and a.attrelid = c.oid
			and a.atttypid = t.oid
			and col.table_name = c.relname
			and col.column_name = a.attname
			and col.table_catalog = current_catalog
			and col.table_schema = current_schema
		order by a.attnum asc
		`,
		tableName,
	)
	finder.InjectionCheck = false
	finder.SelectTotalCount = false
	var cols []*ColumnInfoEntity
	if err := zorm.Query(ctx, finder, &cols, nil); err != nil {
		panic(err)
	}
	tb.Fields = cols

	return tb
}
